
require 'appwiki/base/base_request.rb'
require 'appwiki/base/base_utils.rb'
require 'appwiki/base/base_session.rb'

#
# A base 'servlet' concept that emulates several ruby on rails policies
# The idea is this is subclassed by behavior handlers for specific url patterns or routes
# Controllers are generally invoked by the routing code based on url pattern matching
#

class BaseController

    # useful fields scavenged from inbound http request
    attr_accessor :method
    attr_accessor :params
    attr_accessor :referer
    attr_accessor :domainbase
    attr_accessor :domainreversed

    # local state
    attr_accessor :workingpath
    attr_accessor :requestpath
    attr_accessor :handlerpath
    attr_accessor :flash
    attr_accessor :meth
    attr_accessor :args
    attr_accessor :body
    attr_accessor :session

    # output rules
    attr_accessor :content_type
    attr_accessor :render_nothing
    attr_accessor :current_layout
    attr_accessor :rendered_template
    attr_accessor :redirect

    #######################################################################
    # service raw http requests - usually invoked from the router
    #######################################################################

    # fallbacks
    def whereami() [__FILE__] end
    def default_layout() ["layout","standard.rhtml"] end

    # process one request
    def service(request,response,handlerpath,workingpath,requestpath)

      logmsg "base_controller::service request is #{request.path}" if @@debug

      # copy these here for convenience
      @method = request.method
      @params = request.params
      @referer = request.referer
      @handlerpath = handlerpath
      @workingpath = workingpath
      @requestpath = requestpath

      # reset local state
      @domainbase = request.host.split('.')[-2..-1].join('.')
      @domainreversed = request.host.split('.')[-2..-1].reverse.join("/")
      @rendered_template = false
      @flash = Hash.new
      @meth = nil
      @args = nil
      @body = nil
      @content_type = 'text/html'
      @render_nothing = nil
      @current_layout = default_layout
      @rendered_template = false
      @redirect = nil

      # make or find session; adding to cookies also
      @session = Session.manufacture request.cookies
      hash_before = Marshal.dump(@session).hash if @session

      # go!
      process

      # save session state back to session datastore?
      if @session
        hash_after = Marshal.dump(@session).hash
        # debugging paranoia xxx
        #unless hash_before == hash_after
          #logmsg "saving session #{hash_before} and #{hash_after}" if @@debug
          @session.save
        #end
      end

      # write content type
      response.set_content_type @content_type

      # write cookies
      response.set_cookies

      # write redirect? (this method never responds; it falls off the end of the world.
      response.set_redirect @redirect if @redirect

      # write body?
      response.set_body @body if @body && !@redirect

    end

    ##################################################################################################
    # 'user land' methods that developers typically are going to subclass
    ##################################################################################################

    def method_missing(wh,*therest)
      # xxx internal methods must be protected at some point
    end

    def index
    end

    def default
    end

    def preload
    end

    #
    # render skin if any - a null skin is set by passing [] or "" in call ie: render(:layout => [])
    #
    def postload
      if @current_layout && @current_layout.length > 0
        @content_for_layout = @body
        @body = ""
        render_rhtml(@current_layout)
      end
    end

    #
    # service() invokes process() to resolve the leaf part of the request url
    # this overridable method invokes a preload() and a postload() method
    #
    def process
      # run preload method (which user can subclass)
      preload
      # early out if render nothing is set or if there is already a redirection
      return if @redirect || @render_nothing
      # decide what method on the controller to call
      meth = nil
      if !@workingpath || !@requestpath ||
        ( @workingpath.length >= @requestpath.length && @workingpath.last == @requestpath.last )
        # i have a subtle disambiguation problem between a request for a method whose name
        # matches the controller name itself and a request for the index page - think more. TODO
        meth = "index"
      else
        # normal behavior is to pick a method that matches the leaf of the url passed
        # in ruby on rails this is done at the router level; in appwiki it is at controller scope
        meth = @requestpath.last
        @args = @requestpath.slice(@workingpath.length+1..@requestpath.length+1)
        if !self.respond_to? meth
          @args = [ meth ]
          meth = "default"
        end
      end
      # go!
      if self.respond_to? meth
        send meth
        render :action => meth if !@redirect && !@render_nothing && !@rendered_template
        # TODO one design clumsiness here is that the controller method renders
        # directly to the output buffer, and then the layout erases that buffer and
        # then pipes it to itself... it would be better to have a more generalized
        # idea of diffent output buffers that then layout could composit together,
        # this would support the idea of many fragments making up a page and might
        # also help caching.  even deferred computation of a fragment would be nice.
      end
      # do post load operations (including skinning)
      postload
    end

    ###################################################################
    # escape
    ###################################################################

    # URL escapes a string.
    #
    #   escape("I'd go to the museum straightway!")  
    #     #=> "I%27d+go+to+the+museum+straightway%21"
    #
    def escape(s); s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n){'%'+$1.unpack('H2'*$1.size).join('%').upcase}.tr(' ', '+') end

    #####################################################################
    # unescape
    #####################################################################

    # Unescapes a URL-encoded string.
    #
    #   un("I%27d+go+to+the+museum+straightway%21") 
    #     #=> "I'd go to the museum straightway!"
    #
    def unescape(s); s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){[$1.delete('%')].pack('H*')} end

    ##################################################################################################
    # get files with fallback
    ##################################################################################################

    #
    # implements a fallback policy for finding files; best to use with squid
    #
    def get_rhtml(parts)

      # if the invoker specified a '/' rooted path then try that first
      if parts.length > 1 || parts[0][0] == "/"
        filename = File.join @requestpath[0],parts.join('/')
        #logmsg "base_controller::get_rhtml() 1 trying filename #{filename}" if @@debug
        return File.new(filename).read if File.exists? filename
      end

      # normally look in specified controller folder for this file
      if @handlerpath && @handlerpath.length
        terms = @handlerpath + parts
        filename = terms.join('/')
        #logmsg "base_controller::get_rhtml() 2 trying filename #{filename}" if @@debug
        return File.new(filename).read if File.exists? filename
      end

      # try source areas for controllers; whereami() can be subclassed and use super
      whereami().each do |path|
        terms = path.split('/')[0..-2] + parts
        filename = terms.join('/')
        #logmsg "base_controller::get_rhtml() 5 trying filename #{path} #{terms} #{filename}" if @@debug
        return File.new(filename).read if File.exists? filename
      end

      # fail
      #logmsg "base_controller::get_rhtml() failed to find #{filename}" if @@debug
      return nil

    end

    ##################################################################################################
    # print utils
    ##################################################################################################

    #
    # internal; used by erb
    # 
    def write(what)
      @body = "" if !@body
      @body += what.to_s if what && what.class
    end

    def writeline(what)
      @body = "" if !@body
      @body += what.to_s if what && what.class
      @body += "\n"
    end

    #
    # a template pattern for now, same as render_rhtml_internal...
    #
    def load(name)
      render_rhtml ["#{name}.rhtml"]
      return ""
    end

    #
    # uses erb to do .rhtml support
    #
    def render_rhtml(name)
      logmsg "base_controller::render_rthml rendering #{name}"
      content = get_rhtml(name)
      #logmsg "base_controller::render_rhtml_internal() file not found #{name}" if !content && @@debug
      return nil if !content
      if @body
        temp = body
        @body = ""
        erb = ERB.new(content,nil,nil,"@body")
        erb.result(binding)
        @body = temp + @body
      else
        erb = ERB.new(content,nil,nil,"@body")
        erb.result(binding)
      end
      # something in erb handling is changing the session! xxx
      # @session = Session.find_by_hashid cookies.anselm_sid
      return ""
    end

    #
    # emulation of ruby on rails render() except you can call it successively
    #
    def render(args={})
      return if @redirect
      @render_nothing = args[:nothing]  # TODO hack to block automatic method templates
      json = args[:json]
      action = args[:action]
      html = args[:html]
      content = args[:content_type]
      partial = args[:partial]
      # xxx hack to let me fake scope to other controllers without loading
      temp = @handlerpath
      @handlerpath = args[:controller] if args[:controller]
      @content_type = content if content
      @current_layout = args[:layout] if args[:layout]
      write html if html
      @content_type = 'text/plain' if !content && json
      write json if json
      @render_nothing = true if json
      @rendered_template = true if json
      @current_layout = nil if json
      @rendered_template = true if action
      render_rhtml ["#{action}.rhtml"] if action
      # TODO hack end to return scope to current controller
      @handlerpath = temp
    end

    #
    # store a redirection request and then apply it prior to rendering
    #
    def redirect_to(args={})
      # use an url if specified
      @redirect = args[:url] if args[:url]
      # or build up an action if specified
      @redirect = File.join("/#{@workingpath[1..-1].join('/')}",args[:action].to_s) if args[:action]
      logmsg "base_controller::redirect_to() setting redirect to #{@redirect}" if @@debug
    end

    #
    # link to an action of the current controller
    # later i would like to support linking between controllers
    #
    def link(path="",controller=nil)
      return nil if !@workingpath
      if controller
        path = File.join("/#{controller}","#{path}")
      else
        path = File.join("/#{@workingpath[1..-1].join('/')}","#{path}")
      end
      path = path[0..-2] if path.length && path[-1..-1] == "/"
      return path
    end

end
